﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using RayDen.Library.Components.Color.Types;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Threads;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Corrector.Renderers;
using RayDen.RayEngine.Corrector.Samplers;
using RayDen.RayEngine.Corrector.Types;
using RayDen.RayEngine.Managers;

namespace RayDen.RayEngine.Corrector
{
    public class CorrectorRenderThread : RenderThread, IDisposable
    {
        protected IIntersectionEngine intersectionDevice;
        protected RayBuffer[] rayBuffers;
        protected SampleBuffer<IColorType> sampleBuffer;
        protected Task renderThread;

        protected int width, height;
        protected IFrameManager frameManager;
        protected CorrectorImageFilm pixelDevice;
        private CancellationTokenSource cancelToken;
        protected internal GlobalSamplingContext samplers;

        protected ImagePlaneSample[] Samples;
        protected PathSampleProcessor _pathRayProcessors;

        public CorrectorRenderThread(int index, IRayEngineScene scn) : base(index, scn)
        {
        }

        public CorrectorRenderThread(int index,
                                       IIntersectionEngine device,
                                       RayEngineScene scn,
                                       bool lowLatency,
                                       CorrectorImageFilm pixelDevice,
                                       GlobalSamplingContext sc, IFrameManager fM)
            : base(index, scn)
        {
            this.pixelDevice = pixelDevice;
            this.frameManager = fM;
            Init(device, lowLatency, pixelDevice, sc);
        }

        public void SetSamples(ImagePlaneSample[] smp)
        {
            this.Samples = smp;
            _pathRayProcessors.SetSamples(this.Samples);
        }

        private void Init(IIntersectionEngine device, bool lowLatency, CorrectorImageFilm pixelDevice, GlobalSamplingContext sc)
        {
            this.samplers = sc;
            intersectionDevice = device;
            width = pixelDevice.Width;
            height = pixelDevice.Height;
            var rayBufferSize = lowLatency ? (RayBuffer.RayBufferSize / 8) : RayBuffer.RayBufferSize;
            rayBuffers = new RayBuffer[DeviceRenderBufferCount];
            this.pixelDevice = pixelDevice;
            for (int i = 0; i < DeviceRenderBufferCount; i++)
            {
                rayBuffers[i] = new RayBuffer(rayBufferSize);
                rayBuffers[i].UserData = threadIndex;
            }
            _pathRayProcessors = new PathSampleProcessor(scene.MaxPaths, (RayEngineScene)scene, pixelDevice, samplers);
            //_pathRayProcessors.RequestSamplesFunc = (f)=>{ GenerateIm}


            renderThread = null;
        }


        public void Dispose()
        {
            if (renderThread != null)
            {
                renderThread.Dispose();
                renderThread = null;
            }
        }
        public override void Start()
        {
            base.Start();

            samplers.Init(width, height);


            for (int i = 0; i < DeviceRenderBufferCount; i++)
            {
                rayBuffers[i].Reset();
                rayBuffers[i].ResetUserData();
                rayBuffers[i].UserData = (i);
            }
            _pathRayProcessors.ReInit();
            cancelToken = new CancellationTokenSource();
            // Create the thread for the rendering
            intersectionDevice.Start();
            this.renderThread = Task.Factory.StartNew(() => RenderThreadImpl(cancelToken.Token, this), cancelToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Current);
        }

        protected virtual void ThreadJob(CancellationToken t, CorrectorRenderThread renderThread)
        {
            Tracer.TraceLine("CL Render Thread {0} started", renderThread.threadIndex);
            if (!started)
                return;

#if VERBOSE
            try
            {
#endif
            var todoBuffers = new Queue<RayBuffer>();
            for (var i = 0; i < DeviceRenderBufferCount; i++)
                todoBuffers.Enqueue(renderThread.rayBuffers[i]);

            while (true)
            {
                if ((frameManager.FrameReady || t.IsCancellationRequested))
                {
                    break;
                }
                while (todoBuffers.Count > 0)
                {
                    var rayBuffer = todoBuffers.Dequeue();
                    rayBuffer.Reset();
                    renderThread._pathRayProcessors.FillRayBuffer(rayBuffer);
                    renderThread.intersectionDevice.PushRayBuffer(rayBuffer, renderThread.threadIndex);
                }
                var rayBuff = renderThread.intersectionDevice.PopRayBuffer(renderThread.threadIndex);
                if (rayBuff != null && rayBuff.UserData == renderThread.threadIndex)
                {
                    renderThread._pathRayProcessors.AdvancePaths(rayBuff);
                    todoBuffers.Enqueue(rayBuff);
                }
            }
            started = false;
#if VERBOSE
            }
            catch (Exception ex)
            {
                Tracer.TraceLine("Exiting rendering : Reason \n\r{0}", ex);
            }
#endif
            //frameManager.Complete();
            Tracer.TraceLine("CL Render Thread {0} exited", renderThread.threadIndex);
        }


        protected static void RenderThreadImpl(CancellationToken t, CorrectorRenderThread renderThread)
        {
            renderThread.ThreadJob(t, renderThread);
        }

        public override void Interrupt()
        {
            throw new NotImplementedException();
        }

        public override void ClearPaths()
        {
            throw new NotImplementedException();
        }

        public override int GetPass()
        {
            throw new NotImplementedException();
        }
    }
}
